package org.walkerljl.commons.util;

import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
import org.apache.commons.httpclient.HttpMethodBase;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.*;
import org.apache.commons.httpclient.methods.multipart.*;
import org.apache.commons.httpclient.params.HttpClientParams;
import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
import org.apache.commons.httpclient.params.HttpMethodParams;

import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * HttpUtils
 *
 * @author lijunlin
 */
public class HttpUtils {


    public final static String METHOD_POST = "POST";
    public final static String METHOD_GET = "GET";
    /**
     * 64K缓存块
     */
    private static final int IO_BUFFER_SZIE = 64 * 1024;
    private String url;
    private String method;
    private String charset;
    private int connectTimeout = 5000;
    private int readTimeout = 1000 * 60;
    private Map<String, String> header = new LinkedHashMap<String, String>();
    private List<Parameter> params = new ArrayList<Parameter>();
    private RequestEntity requestEntity;

    public HttpUtils(String url) {
        this.url = url;
    }

    public HttpUtils(String url, String method) {
        this.url = url;
        this.method = method;
    }

    /**
     * 使用了该方法，之前设置的addParameter值会全部失效。
     *
     * @param requestEntity
     */
    public void setRequestEntity(RequestEntity requestEntity) {
        this.requestEntity = requestEntity;
    }

    public void setRequestData(String data, String contentType) {
        try {
            this.requestEntity = new StringRequestEntity(data, contentType, getCharset());
        } catch (Exception e) {
            ;
        }
    }

    public void executeToOutputStream(OutputStream out) throws Exception {
        HttpMethodBase method = executeRequest();
        InputStream in = method.getResponseBodyAsStream();
        try {
            byte[] buffer = new byte[IO_BUFFER_SZIE];
            int read;
            while (true) {
                read = in.read(buffer, 0, IO_BUFFER_SZIE);
                if (read == -1) {
                    break;
                }
                out.write(buffer, 0, read);
            }
        } finally {
            if (in != null) {
                in.close();
            }
        }
    }

    public InputStream download() throws Exception {
        HttpMethodBase method = executeRequest();
        return method.getResponseBodyAsStream();
    }

    public String execute() throws Exception {
        return executeRequest().getResponseBodyAsString();
    }

    public long getContentLength() {
        try {
            HttpMethodBase method = executeRequest();
            return method.getResponseContentLength();
        } catch (Exception e) {
            return Long.MAX_VALUE;
        }
    }

    public HttpMethodBase executeRequest() throws Exception {
        HttpMethodBase method = null;
        if ("POST".equals(getMethod())) {
            method = new PostMethod(getUrl());
        } else if ("PUT".equals(getMethod())) {
            method = new PutMethod(getUrl());
        } else if ("DELETE".equals(getMethod())) {
            method = new DeleteMethod(getUrl());
        } else if ("HEAD".equals(getMethod())) {
            method = new HeadMethod(getUrl());
        } else if ("OPTIONS".equals(getMethod())) {
            method = new OptionsMethod(getUrl());
        } else if ("TRACE".equals(getMethod())) {
            method = new TraceMethod(getUrl());
        } else {
            method = new GetMethod(getUrl());
        }
        // 设置头信息
        method.setRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36");
        method.setRequestHeader("Connection", "Keep-Alive");
        method.setRequestHeader("Accept-Charset", getCharset());
        // 设置参数信息
        if ("POST".equals(getMethod())) {
            String contentType = header.get("Content-Type");
            if ("multipart/form-data".equals(contentType)) {
                ((PostMethod) method).setRequestEntity(new MultipartRequestEntity(getPartList(), method.getParams()));
                header.remove("Content-Type");
            } else {
                ((PostMethod) method).addParameters(getPairList());
            }
            if (requestEntity != null) {
                ((PostMethod) method).setRequestEntity(requestEntity);
            }
        } else if (method.getQueryString() == null || "".equals(method.getQueryString())) {
            method.setQueryString(getQueryString());
        }

        for (Entry<String, String> entry : header.entrySet()) {
            method.setRequestHeader(entry.getKey(), entry.getValue());
        }

        org.apache.commons.httpclient.HttpClient client = new org.apache.commons.httpclient.HttpClient();
        HttpClientParams clientParams = client.getParams();
        clientParams.setContentCharset(getCharset());

        HttpConnectionManagerParams params = client.getHttpConnectionManager().getParams();
        params.setConnectionTimeout(connectTimeout);
        params.setSoTimeout(readTimeout);

        //发生异常自动重试3次
        int retry = 3;
        if (retry > 0) {
            params.setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler(3, true));
        }
        int rescode = client.executeMethod(method);
        if (rescode != HttpStatus.SC_OK) {
            String result = method.getResponseBodyAsString();
            throw new IllegalAccessException("Http request error code :" + rescode + "\n response result: " + result);
        }
        return method;
    }

    public void addParameter(String key, Object value) {
        if (key != null && value != null) {
            params.add(new Parameter(key, value));
        }
    }

    public void addHeader(String key, String value) {
        header.put(key, value);
    }

    private Part[] getPartList() throws Exception {
        List<Part> data = new ArrayList<Part>();
        for (Parameter entry : params) {
            if (entry.getKey() == null || entry.getValue() == null) {
                continue;
            }
            PartBase part = null;
            if (entry.getValue() instanceof File) {
                part = new FilePart(entry.getKey(), (File) entry.getValue());
            } else {
                part = new StringPart(entry.getKey(), String.valueOf(entry.getValue()), getCharset());
            }
            data.add(part);
        }
        return data.toArray(new Part[data.size()]);
    }

    private NameValuePair[] getPairList() throws Exception {
        List<NameValuePair> data = new ArrayList<NameValuePair>();
        for (Parameter entry : params) {
            if (entry.getKey() == null || entry.getValue() == null) {
                continue;
            }
            data.add(new NameValuePair(entry.getKey(), String.valueOf(entry.getValue())));
        }
        return data.toArray(new NameValuePair[data.size()]);
    }

    private String getQueryString() {
        StringBuilder sb = new StringBuilder();
        int counter = 0;
        for (Parameter entry : params) {
            if (counter != 0) {
                sb.append("&");
            }
            if (entry.getKey() != null && entry.getValue() != null) {
                sb.append(entry.getKey()).append("=").append(encodeURI(String.valueOf(entry.getValue())));
            }
            counter++;
        }
        return sb.toString();
    }

    private String encodeURI(String str) {
        try {
            return URLEncoder.encode(str, "UTF-8");
        } catch (Exception e) {
            return str;
        }
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getMethod() {
        if (method == null || "".equals(method)) {
            method = "POST";
        }
        return method;
    }

    public void setMethod(String method) {
        this.method = method;
    }

    public String getCharset() {
        if (charset == null || "".equals(charset)) {
            charset = "UTF-8";
        }
        return charset;
    }

    public void setCharset(String charset) {
        this.charset = charset;
    }

    public void setConnectTimeout(int connectTimeout) {
        this.connectTimeout = connectTimeout;
    }

    public void setReadTimeout(int readTimeout) {
        this.readTimeout = readTimeout;
    }

    private class Parameter {
        private String key;
        private Object value;

        public Parameter(String key, Object value) {
            this.key = key;
            this.value = value;
        }

        public String getKey() {
            return key;
        }

        public Object getValue() {
            return value;
        }
    }
}
